<template>
  <CustomScrollbar ref="scroller" :on-load-more="loadMore" class="chat-message-box" reverse>
    <ul ref="list" style="padding: 0 15px" @click="onListClick">
      <li v-if="page.current <= page.pages" class="load-more" @click="loadMore">
        <p v-show="loading && page.current !== 1"><i class="el-icon-loading"></i> 正在加载...</p>
        <p v-show="delay && !loading"><i class="el-icon-time"></i> 查看更多...</p>
      </li>
      <li v-for="(item, index) in messages" :key="item.id" :data-mid="item.id" class="chat-message-item">
        <!--回复时间-->
        <p v-if="shouldShowTime(item, index)" class="time">
          <span class="time-value" :title="item.createdAt">{{ friendlyTime(item.createdAt) }}</span>
        </p>
        <p v-if="item.type === 'recall'" class="time">
          <span class="time-value" :title="item.updatedAt">
            <span style="color: #379de7">{{ item.recall?.userId === userId ? '你' : item.recall.name ?? '系统' }}</span>
            撤回了一条<span style="color: #379de7">{{
              item.recall?.userId === item.sender.userId
                ? ''
                : item.sender?.userId === userId
                ? '你的'
                : `${item.sender.name}的`
            }}</span
            >消息
          </span>
        </p>
        <!--非移出群聊消息-->
        <div v-else-if="item.type !== 'remove'" :class="['message', { self: item.sender.userId === userId }]">
          <!--有头像-->
          <img
            v-if="item.sender.avatar"
            :title="item.sender.name"
            class="avatar"
            :src="$withContext(item.sender.avatar)"
            alt="" />
          <!--没头像-->
          <span v-else :title="item.sender.name" class="avatar">
            {{ item.sender ? (item.sender.userId === userId ? '我' : item.sender.name.substr(-2)) : '' }}
          </span>
          <!--消息内容-->
          <div class="chat-message">
            <p class="chat-sender-name">{{ item.sender.name }}</p>
            <div class="text chat-message-content" @contextmenu.prevent="event => showContextMenu(item, event)">
              <div v-html="parseMsg(item)"></div>
              <blockquote v-if="item.replyMessage" class="reply-message-blockquote">
                <p class="reply-message">
                  <span class="reply-message-user-name">{{ item.replyMessage.sender.name }}</span>
                  <span class="reply-message-time">{{ item.createdAt }}</span>
                </p>
                <p class="reply-message-content" v-html="parseMsg(item.replyMessage)"></p>
              </blockquote>
            </div>
          </div>
        </div>
        <!--移出群聊-->
        <p v-else class="time">
          <span class="time-value" :title="item.createdAt">{{ item.content }}</span>
        </p>
      </li>
    </ul>
    <ContextMenu ref="contextMenu" append-to-body :list="menu" @on-item-click="onMenuItemClick" />
    <EasyPopup ref="popup" class="info-popup" flex-column interactive append-to-body>
      <div class="flex-row">
        <img
          :src="$withContext(selectedSender.avatar) || require('@/assets/images/avatar.png')"
          class="avatar"
          alt="" />
        <div>
          <h3 class="info-item">{{ selectedSender.name }}</h3>
          <p class="info-item"><span>姓名:</span> {{ selectedSender.name }}</p>
          <p class="info-item"><span>部门:</span> {{ selectedSender.deptName }}</p>
          <p class="info-item"><span>帐号:</span> {{ selectedSender.username }}</p>
          <p class="info-item"><span>联系方式:</span> {{ selectedSender.phone }}</p>
        </div>
      </div>
      <ElButton style="margin-top: auto" size="small" type="primary" @click="sendMessage">发消息</ElButton>
    </EasyPopup>
  </CustomScrollbar>
</template>

<script>
  import { mapMutations, mapState } from 'vuex'
  import { delay, filterHtml, findParent, oneMinuteMillis, toMilliseconds } from '@web-utils/core'
  import { friendlyTime } from '@web-utils/integrations/dayjs'
  import { downloadFile, onLoadComplete, parseMsg } from '@/components/im/index'
  import message from '@/vue/mixin/message'
  import { openFile } from '@/api/file-view'
  import { getMessage, recall } from './api/message'
  import { newThread } from '@/components/im/api/thread'

  export default {
    name: 'ChatMessageBox',
    mixins: [message],
    inject: {
      chatClient: {
        default: ''
      }
    },
    message: {
      chat(data) {
        const message = data.content.content
        if (message.thread.id === this.session.thread?.id) {
          this.append(message)
        }
      },
      recall(data) {
        const content = JSON.parse(JSON.parse(data.content).content)
        const message = this.messages.find(value => value.id === content.id)
        if (message) {
          message.type = 'recall'
          message.recall = content.recall
        }
      },
      addMember(data) {
        const message = JSON.parse(JSON.parse(data.content).content)
        if (message.groupId === this.session.group?.id) {
          this.reload(false)
        }
      },
      removeMember(data) {
        const message = JSON.parse(JSON.parse(data.content).content)
        if (message.groupId === this.session.group?.id) {
          this.reload(false)
        }
      }
    },
    props: {
      session: {
        type: Object,
        default: () => {}
      }
    },
    data() {
      return {
        loading: false,
        delay: false,
        messages: [],
        selectedSender: {},
        selectedMessage: {},
        page: {
          current: 1,
          size: 50,
          pages: 1
        }
      }
    },
    computed: {
      ...mapState('user', {
        userId: state => state.sysUser.userId
      }),
      menu() {
        const menu = []

        if (!this.selectedMessage?.sender) {
          return menu
        }

        if (this.selectedMessage.sender.userId !== this.userId) {
          menu.push({
            text: '回复',
            action: 'reply',
            icon: 'el-icon-s-comment'
          })
        }

        if (
          (this.selectedMessage.sender.userId === this.userId ||
            (this.chatClient.currentSession.type === 'group' && this.chatClient.$refs.chatGroupUsers?.me.role) > 0) &&
          Date.now() - toMilliseconds(this.selectedMessage.createdAt) < 3 * 60 * 1000
        ) {
          menu.push({
            text: '撤回',
            action: 'recall',
            icon: 'el-icon-refresh-left'
          })
        }

        return menu
      }
    },
    watch: {
      session: {
        immediate: true,
        handler: 'reload'
      }
    },
    activated() {
      this.scrollBottom()
    },
    beforeDestroy() {
      if (this.audio) {
        this.$el.removeChild(this.audio)
      }
    },
    methods: {
      ...mapMutations('im', ['setActiveThreadId']),
      friendlyTime,
      parseMsg(message) {
        return parseMsg(message)
      },
      async onListClick(event) {
        const target = event.target
        if (target.classList.contains('file-item-download')) {
          await downloadFile(event)
        } else if (target.classList.contains('avatar')) {
          const item = findParent(target, '.chat-message-item')
          const message = this.messages.find(value => value.id === item.dataset.mid)
          this.selectedSender = message.sender
          if (this.selectedSender) {
            this.$refs.popup.show(event)
          } else {
            // 获取不到发送人信息
          }
        } else if (target.classList.contains('file-item-name') || target.classList.contains('chat-image')) {
          const item = findParent(target, '.chat-message-item')
          const message = this.messages.find(value => value.id === item.dataset.mid)
          await openFile(
            message.media,
            this.messages.filter(value => value.media).map(value => value.media)
          )
        }
      },
      shouldShowTime(item, index) {
        return index === 0 || item.timestamp - this.messages[index - 1].timestamp > oneMinuteMillis
      },
      scrollBottom() {
        this.$nextTick(() => {
          this.$refs.scroller && this.$refs.scroller?.scrollBottom()
        })
      },
      async fetchMsg(append = false) {
        if (!this.session.thread) {
          return
        }
        if (this.delay || this.loading || this.page.current > this.page.pages) {
          return
        }
        if (append) {
          this.delay = true
          await delay(300)
          this.delay = false
        }
        try {
          this.loading = true
          const { current, size } = this.page
          let res = await getMessage({
            current,
            size,
            descs: ['createdAt'],
            example: { 'thread.id': this.session.thread.id }
          })
          res = res.data
          this.page.current++
          this.page.pages = res.data.totalPages
          if (append) {
            this.messages.unshift(...res.data.content.reverse())
          } else {
            this.messages = res.data.content.reverse()
          }

          const oldScrollHeight = this.$refs.scroller?.getScrollHeight()
          const diff = this.getDiff()
          await this.$nextTick()
          if (append) {
            this.$refs.scroller?.setScrollTop(this.$refs.scroller?.getScrollHeight() - oldScrollHeight)
          } else {
            if (diff > 10) {
              return
            }
            await this.scrollToBottomOnImageLoaded()
          }
        } catch (e) {
          console.error(e)
        }
        this.loading = false
      },
      async reload() {
        this.messages = []
        this.page.current = 1
        this.page.pages = 1
        await this.fetchMsg()
      },
      getDiff() {
        return Math.abs(
          this.$refs.scroller?.getScrollHeight() -
            (this.$refs.scroller?.viewport.scrollTop + this.$refs.scroller?.viewport.offsetHeight)
        )
      },
      async append(message) {
        const diff = this.getDiff()
        this.messages.push(message)
        if (diff > 10) {
          return
        }
        await this.$nextTick()
        await this.scrollToBottomOnImageLoaded()
      },
      async scrollToBottomOnImageLoaded() {
        this.scrollBottom()
        const images = this.$refs.list?.querySelectorAll('.chat-image')
        if (images?.length) {
          await onLoadComplete(images[images.length - 1])
        }
        this.scrollBottom()
      },
      async loadMore() {
        await this.fetchMsg(true)
      },
      async sendMessage() {
        if (
          this.session.type === 'group' &&
          this.selectedSender?.userId &&
          this.userId !== this.selectedSender.userId
        ) {
          const res = await newThread({
            userIds: [this.selectedSender.userId],
            type: 'p2p'
          })
          this.setActiveThreadId(res.data.data.id)
        } else if (
          this.session.type === 'p2p' &&
          this.selectedSender?.userId &&
          this.userId !== this.selectedSender.userId
        ) {
          this.scrollBottom()
          this.chatClient.$refs.editor.editor.focus(true)
        }
        this.$refs.popup.hide()
      },
      showContextMenu(message, event) {
        this.selectedMessage = message
        this.$refs.contextMenu.show(event)
      },
      filterHtml(message) {
        if (message.type === 'image') {
          return '[图片]'
        }
        if (message.type === 'file') {
          return '[文件]'
        }
        return filterHtml(message?.content ?? '')
      },
      async onMenuItemClick(item) {
        await this[item.action]?.()
      },
      reply() {
        this.chatClient.$refs.editor.editor.clear()
        this.chatClient.$refs.editor.editor.insertNode({
          type: 'reply',
          id: this.selectedMessage.id,
          userName: this.selectedMessage.sender.name,
          time: this.selectedMessage.createdAt,
          content: this.parseMsg(this.selectedMessage),
          children: [{ text: '' }]
        })
        this.chatClient.$refs.editor.editor.focus()
      },
      async recall() {
        const res = await recall(this.selectedMessage.id)
        this.selectedMessage.type = res.data.data.type
        this.selectedMessage.recall = res.data.data.recall
      }
    }
  }
</script>

<style lang="scss">
  .chat-message-box {
    height: 100%;

    .chat-message-item {
      padding-top: 15px;
    }

    .message {
      @include flex-row;
    }

    // 名字头像
    .avatar {
      display: inline-block;
      width: 34px;
      height: 34px;
      margin-right: 10px;
      color: #fff;
      font-weight: bold;
      font-size: 0.9em;
      line-height: 34px;
      text-align: center;
      background-color: #c8ceda;
      border-radius: 50%;
      cursor: pointer;
    }

    .time {
      margin-bottom: 6px;
      text-align: center;

      &::before {
        border: 6px solid transparent;
        border-right-color: white;
      }

      .time-value {
        display: inline-block;
        padding: 2px 10px;
        color: #fff;
        font-size: 12px;
        background-color: #dcdcdc;
        border-radius: 12px;
      }
    }

    .text {
      position: relative;
      display: inline-block;
      max-width: calc(100% - 90px);
      padding: 10px;
      font-size: 12px;
      text-align: left;
      word-break: break-all;
      background-color: #fff;
      border-radius: 4px;

      &::before {
        position: absolute;
        top: 9px;
        right: 100%;
        border: 6px solid transparent;
        border-right-color: #fff;
        content: ' ';
      }
    }

    .self {
      flex-flow: row-reverse;
      text-align: right;

      .avatar {
        float: right;
        margin: 0 0 0 10px;
      }

      .text {
        background-color: #b2e281;

        &::before {
          right: inherit;
          left: 100%;
          border-right-color: transparent;
          border-left-color: #b2e281;
        }
      }
    }

    .load-more {
      color: #2d8cf0;
      font-size: 0.8em;
      text-align: center;
      cursor: default;
    }

    .chat-message {
      @include flex-item;
      position: relative;
      top: -10px;
    }

    .chat-sender-name {
      margin-bottom: 2px;
      color: #888;
      font-size: 12px;
    }
  }

  .info-popup {
    .avatar {
      display: inline-block;
      width: 50px;
      height: 50px;
      margin-right: 10px;
      background-color: #ccc;
      border-radius: 4px;
    }

    .info-item {
      margin-bottom: 4px;
      color: #888;

      &:first-child {
        color: black;
      }

      &:last-child {
        margin-bottom: 0;
      }
    }
  }
</style>
